﻿using System.Linq;
using JetBrains.Annotations;
using JetBrains.Metadata.Reader.API;
using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Modules;

// ReSharper disable once CheckNamespace
namespace MugenMvvmToolkit.ReSharper
{
    public class MethodProvider
    {
        #region Fields

        private readonly TypeProvider _typeProvider;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="MethodProvider" /> class.
        /// </summary>
        private MethodProvider([NotNull] IPsiModule psiModule, [NotNull] IModuleReferenceResolveContext resolveContext)
        {
            _typeProvider = TypeProvider.Create(psiModule, resolveContext);
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="MethodProvider" /> class.
        /// </summary>
        private MethodProvider([NotNull] ICSharpContextActionDataProvider provider)
        {
            _typeProvider = TypeProvider.Create(provider);
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="MethodProvider" /> class.
        /// </summary>
        private MethodProvider(TypeProvider typeProvider)
        {
            _typeProvider = typeProvider;
        }

        #endregion

        #region Properties

        public TypeProvider TypeProvider
        {
            get { return _typeProvider; }
        }

        [CanBeNull]
        public IMethod ShouldNotBeNull
        {
            get { return FindMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeNull, 2); }
        }

        [CanBeNull]
        public IMethod ShouldNotBeNullOrEmptyString
        {
            get { return FindMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeNullOrEmpty, 2); }
        }

        [CanBeNull]
        public IMethod ShouldNotBeNullOrWhitespace
        {
            get { return FindMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeNullOrWhitespace, 2); }
        }

        [CanBeNull]
        public IMethod ShouldNotBeNullOrEmptyEnumerable
        {
            get { return FindGenericMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeNullOrEmpty, 1); }
        }

        [CanBeNull]
        public IMethod ShouldNotBeNullOrDefault
        {
            get { return FindGenericMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeNullOrDefault, 1); }
        }

        [CanBeNull]
        public IMethod ShouldNotBeDefault
        {
            get { return FindGenericMethod(_typeProvider.Should, MethodNameConstants.ShouldNotBeDefault, 1); }
        }

        [CanBeNull]
        public IMethod ViewModelBaseGetMethod
        {
            get { return FindMethod(_typeProvider.ViewModelBase, MethodNameConstants.ViewModelBaseGetMethod, 3); }
        }

        [CanBeNull]
        public IMethod ViewModelBaseGetMethodGeneric
        {
            get { return FindGenericMethod(_typeProvider.ViewModelBase, MethodNameConstants.ViewModelBaseGetMethod, 1, 1); }
        }

        [CanBeNull]
        public IMethod ViewModelBaseGetMethodGenericWithName
        {
            get { return FindGenericMethod(_typeProvider.ViewModelBase, MethodNameConstants.ViewModelBaseGetMethod, 1, 2); }
        }

        [CanBeNull]
        public IMethod ValidatableViewModelInterfaceValidate
        {
            get { return FindMethod(_typeProvider.ValidatableViewModelInterface, MethodNameConstants.ValidatableViewModelInterfaceValidate, 1); }
        }

        [CanBeNull]
        public IMethod CloseableViewModelInterfaceClose
        {
            get { return FindMethod(_typeProvider.CloseableViewModelInterface, MethodNameConstants.CloseableViewModelInterfaceClose, 1); }
        }

        [CanBeNull]
        public IMethod ViewModelExtensionsWrapToGeneric
        {
            get { return FindGenericMethod(_typeProvider.ViewModelExtensions, MethodNameConstants.ViewModelExtensionsWrapTo, 1); }
        }

        #endregion

        #region Methods

        public static MethodProvider Create([NotNull] IPsiModule psiModule,
            [NotNull] IModuleReferenceResolveContext resolveContext)
        {
            return new MethodProvider(psiModule, resolveContext);
        }

        public static MethodProvider Create([NotNull] ICSharpContextActionDataProvider provider)
        {
            return new MethodProvider(provider);
        }

        public static MethodProvider Create(TypeProvider typeProvider)
        {
            return new MethodProvider(typeProvider);
        }

        [CanBeNull]
        private static IMethod FindMethod(IDeclaredType type, string methodName, int parametersCount)
        {
            ITypeElement typeElement = type.GetTypeElement();
            if (typeElement == null)
                return null;
            return typeElement
                .Methods
                .SingleOrDefault(method =>
                    method.ShortName == methodName && method.Parameters.Count == parametersCount &&
                    method.TypeParameters.Count == 0);
        }

        [CanBeNull]
        private static IMethod FindGenericMethod(IDeclaredType type, string methodName, int genericParametersCount)
        {
            ITypeElement typeElement = type.GetTypeElement();
            if (typeElement == null)
                return null;
            return typeElement.Methods
                .SingleOrDefault(
                    method => method.TypeParameters.Count == genericParametersCount && method.ShortName == methodName);
        }

        [CanBeNull]
        private static IMethod FindGenericMethod(IDeclaredType type, string methodName, int genericParametersCount, int parametersCount)
        {
            ITypeElement typeElement = type.GetTypeElement();
            if (typeElement == null)
                return null;
            return typeElement
                .Methods
                .SingleOrDefault(method => method.TypeParameters.Count == genericParametersCount
                    && method.ShortName == methodName && method.Parameters.Count == parametersCount);
        }

        #endregion
    }
}